<template>
    <view class="uni-datetime-picker">
        <view @click="initTimePicker">
            <slot>
                <view
                    class="uni-datetime-picker-timebox-pointer"
                    :class="{ 'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border }"
                >
                    <text class="uni-datetime-picker-text">{{ time }}</text>
                    <view v-if="!time" class="uni-datetime-picker-time">
                        <text class="uni-datetime-picker-text">{{ selectTimeText }}</text>
                    </view>
                </view>
            </slot>
        </view>
        <view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
        <view
            v-if="visible"
            class="uni-datetime-picker-popup"
            :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
            :style="fixNvueBug"
        >
            <view class="uni-title">
                <text class="uni-datetime-picker-text">{{ selectTimeText }}</text>
            </view>
            <view v-if="dateShow" class="uni-datetime-picker__container-box">
                <picker-view
                    class="uni-datetime-picker-view"
                    :indicator-style="indicatorStyle"
                    :value="ymd"
                    @change="bindDateChange"
                >
                    <picker-view-column>
                        <view v-for="(item, index) in years" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view v-for="(item, index) in months" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view v-for="(item, index) in days" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                </picker-view>
                <!-- 兼容 nvue 不支持伪类 -->
                <text class="uni-datetime-picker-sign sign-left">-</text>
                <text class="uni-datetime-picker-sign sign-right">-</text>
            </view>
            <view v-if="timeShow" class="uni-datetime-picker__container-box">
                <picker-view
                    class="uni-datetime-picker-view"
                    :class="[hideSecond ? 'time-hide-second' : '']"
                    :indicator-style="indicatorStyle"
                    :value="hms"
                    @change="bindTimeChange"
                >
                    <picker-view-column>
                        <view v-for="(item, index) in hours" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view v-for="(item, index) in minutes" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column v-if="!hideSecond">
                        <view v-for="(item, index) in seconds" :key="index" class="uni-datetime-picker-item">
                            <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
                        </view>
                    </picker-view-column>
                </picker-view>
                <!-- 兼容 nvue 不支持伪类 -->
                <text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
                <text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
            </view>
            <view class="uni-datetime-picker-btn">
                <view @click="clearTime">
                    <text class="uni-datetime-picker-btn-text">{{ clearText }}</text>
                </view>
                <view class="uni-datetime-picker-btn-group">
                    <view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
                        <text class="uni-datetime-picker-btn-text">{{ cancelText }}</text>
                    </view>
                    <view @click="setTime">
                        <text class="uni-datetime-picker-btn-text">{{ okText }}</text>
                    </view>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
import { initVueI18n } from "@dcloudio/uni-i18n";
import i18nMessages from "./i18n/index.js";
const { t } = initVueI18n(i18nMessages);
import { fixIosDateFormat } from "./util";

/**
 * DatetimePicker 时间选择器
 * @description 可以同时选择日期和时间的选择器
 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
 * @property {String} type = [datetime | date | time] 显示模式
 * @property {Boolean} multiple = [true|false] 是否多选
 * @property {String|Number} value 默认值
 * @property {String|Number} start 起始日期或时间
 * @property {String|Number} end 起始日期或时间
 * @property {String} return-type = [timestamp | string]
 * @event {Function} change  选中发生变化触发
 */

export default {
    name: "UniDatetimePicker",
    props: {
        type: {
            type: String,
            default: "datetime"
        },
        value: {
            type: [String, Number],
            default: ""
        },
        modelValue: {
            type: [String, Number],
            default: ""
        },
        start: {
            type: [Number, String],
            default: ""
        },
        end: {
            type: [Number, String],
            default: ""
        },
        returnType: {
            type: String,
            default: "string"
        },
        disabled: {
            type: [Boolean, String],
            default: false
        },
        border: {
            type: [Boolean, String],
            default: true
        },
        hideSecond: {
            type: [Boolean, String],
            default: false
        }
    },
    data() {
        return {
            indicatorStyle: `height: 50px;`,
            visible: false,
            fixNvueBug: {},
            dateShow: true,
            timeShow: true,
            title: "日期和时间",
            // 输入框当前时间
            time: "",
            // 当前的年月日时分秒
            year: 1920,
            month: 0,
            day: 0,
            hour: 0,
            minute: 0,
            second: 0,
            // 起始时间
            startYear: 1920,
            startMonth: 1,
            startDay: 1,
            startHour: 0,
            startMinute: 0,
            startSecond: 0,
            // 结束时间
            endYear: 2120,
            endMonth: 12,
            endDay: 31,
            endHour: 23,
            endMinute: 59,
            endSecond: 59
        };
    },
    options: {
        // #ifdef MP-TOUTIAO
        virtualHost: false,
        // #endif
        // #ifndef MP-TOUTIAO
        virtualHost: true
        // #endif
    },
    computed: {
        // 当前年、月、日、时、分、秒选择范围
        years() {
            return this.getCurrentRange("year");
        },

        months() {
            return this.getCurrentRange("month");
        },

        days() {
            return this.getCurrentRange("day");
        },

        hours() {
            return this.getCurrentRange("hour");
        },

        minutes() {
            return this.getCurrentRange("minute");
        },

        seconds() {
            return this.getCurrentRange("second");
        },

        // picker 当前值数组
        ymd() {
            return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay];
        },
        hms() {
            return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond];
        },

        // 当前 date 是 start
        currentDateIsStart() {
            return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay;
        },

        // 当前 date 是 end
        currentDateIsEnd() {
            return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay;
        },

        // 当前年、月、日、时、分、秒的最小值和最大值
        minYear() {
            return this.startYear;
        },
        maxYear() {
            return this.endYear;
        },
        minMonth() {
            if (this.year === this.startYear) {
                return this.startMonth;
            } else {
                return 1;
            }
        },
        maxMonth() {
            if (this.year === this.endYear) {
                return this.endMonth;
            } else {
                return 12;
            }
        },
        minDay() {
            if (this.year === this.startYear && this.month === this.startMonth) {
                return this.startDay;
            } else {
                return 1;
            }
        },
        maxDay() {
            if (this.year === this.endYear && this.month === this.endMonth) {
                return this.endDay;
            } else {
                return this.daysInMonth(this.year, this.month);
            }
        },
        minHour() {
            if (this.type === "datetime") {
                if (this.currentDateIsStart) {
                    return this.startHour;
                } else {
                    return 0;
                }
            }
            if (this.type === "time") {
                return this.startHour;
            }
        },
        maxHour() {
            if (this.type === "datetime") {
                if (this.currentDateIsEnd) {
                    return this.endHour;
                } else {
                    return 23;
                }
            }
            if (this.type === "time") {
                return this.endHour;
            }
        },
        minMinute() {
            if (this.type === "datetime") {
                if (this.currentDateIsStart && this.hour === this.startHour) {
                    return this.startMinute;
                } else {
                    return 0;
                }
            }
            if (this.type === "time") {
                if (this.hour === this.startHour) {
                    return this.startMinute;
                } else {
                    return 0;
                }
            }
        },
        maxMinute() {
            if (this.type === "datetime") {
                if (this.currentDateIsEnd && this.hour === this.endHour) {
                    return this.endMinute;
                } else {
                    return 59;
                }
            }
            if (this.type === "time") {
                if (this.hour === this.endHour) {
                    return this.endMinute;
                } else {
                    return 59;
                }
            }
        },
        minSecond() {
            if (this.type === "datetime") {
                if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
                    return this.startSecond;
                } else {
                    return 0;
                }
            }
            if (this.type === "time") {
                if (this.hour === this.startHour && this.minute === this.startMinute) {
                    return this.startSecond;
                } else {
                    return 0;
                }
            }
        },
        maxSecond() {
            if (this.type === "datetime") {
                if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
                    return this.endSecond;
                } else {
                    return 59;
                }
            }
            if (this.type === "time") {
                if (this.hour === this.endHour && this.minute === this.endMinute) {
                    return this.endSecond;
                } else {
                    return 59;
                }
            }
        },

        /**
         * for i18n
         */
        selectTimeText() {
            return t("uni-datetime-picker.selectTime");
        },
        okText() {
            return t("uni-datetime-picker.ok");
        },
        clearText() {
            return t("uni-datetime-picker.clear");
        },
        cancelText() {
            return t("uni-datetime-picker.cancel");
        }
    },
    watch: {
        // #ifndef VUE3
        value: {
            handler(newVal) {
                if (newVal) {
                    this.parseValue(fixIosDateFormat(newVal));
                    this.initTime(false);
                } else {
                    this.time = "";
                    this.parseValue(Date.now());
                }
            },
            immediate: true
        },
        // #endif
        // #ifdef VUE3
        modelValue: {
            handler(newVal) {
                if (newVal) {
                    this.parseValue(fixIosDateFormat(newVal));
                    this.initTime(false);
                } else {
                    this.time = "";
                    this.parseValue(Date.now());
                }
            },
            immediate: true
        },
        // #endif
        type: {
            handler(newValue) {
                if (newValue === "date") {
                    this.dateShow = true;
                    this.timeShow = false;
                    this.title = "日期";
                } else if (newValue === "time") {
                    this.dateShow = false;
                    this.timeShow = true;
                    this.title = "时间";
                } else {
                    this.dateShow = true;
                    this.timeShow = true;
                    this.title = "日期和时间";
                }
            },
            immediate: true
        },
        start: {
            handler(newVal) {
                this.parseDatetimeRange(fixIosDateFormat(newVal), "start");
            },
            immediate: true
        },
        end: {
            handler(newVal) {
                this.parseDatetimeRange(fixIosDateFormat(newVal), "end");
            },
            immediate: true
        },

        // 月、日、时、分、秒可选范围变化后，检查当前值是否在范围内，不在则当前值重置为可选范围第一项
        months(newVal) {
            this.checkValue("month", this.month, newVal);
        },
        days(newVal) {
            this.checkValue("day", this.day, newVal);
        },
        hours(newVal) {
            this.checkValue("hour", this.hour, newVal);
        },
        minutes(newVal) {
            this.checkValue("minute", this.minute, newVal);
        },
        seconds(newVal) {
            this.checkValue("second", this.second, newVal);
        }
    },

    mounted() {
        // #ifdef APP-NVUE
        const res = uni.getSystemInfoSync();
        this.fixNvueBug = {
            top: res.windowHeight / 2,
            left: res.windowWidth / 2
        };
        // #endif
    },

    methods: {
        /**
         * @param {Object} item
         * 小于 10 在前面加个 0
         */

        lessThanTen(item) {
            return item < 10 ? "0" + item : item;
        },

        /**
         * 解析时分秒字符串，例如：00:00:00
         * @param {String} timeString
         */
        parseTimeType(timeString) {
            if (timeString) {
                let timeArr = timeString.split(":");
                this.hour = Number(timeArr[0]);
                this.minute = Number(timeArr[1]);
                this.second = Number(timeArr[2]);
            }
        },

        /**
         * 解析选择器初始值，类型可以是字符串、时间戳，例如：2000-10-02、'08:30:00'、 1610695109000
         * @param {String | Number} datetime
         */
        initPickerValue(datetime) {
            let defaultValue = null;
            if (datetime) {
                defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end);
            } else {
                defaultValue = Date.now();
                defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end);
            }
            this.parseValue(defaultValue);
        },

        /**
         * 初始值规则：
         * - 用户设置初始值 value
         * 	- 设置了起始时间 start、终止时间 end，并 start < value < end，初始值为 value， 否则初始值为 start
         * 	- 只设置了起始时间 start，并 start < value，初始值为 value，否则初始值为 start
         * 	- 只设置了终止时间 end，并 value < end，初始值为 value，否则初始值为 end
         * 	- 无起始终止时间，则初始值为 value
         * - 无初始值 value，则初始值为当前本地时间 Date.now()
         * @param {Object} value
         * @param {Object} dateBase
         */
        compareValueWithStartAndEnd(value, start, end) {
            let winner = null;
            value = this.superTimeStamp(value);
            start = this.superTimeStamp(start);
            end = this.superTimeStamp(end);

            if (start && end) {
                if (value < start) {
                    winner = new Date(start);
                } else if (value > end) {
                    winner = new Date(end);
                } else {
                    winner = new Date(value);
                }
            } else if (start && !end) {
                winner = start <= value ? new Date(value) : new Date(start);
            } else if (!start && end) {
                winner = value <= end ? new Date(value) : new Date(end);
            } else {
                winner = new Date(value);
            }

            return winner;
        },

        /**
         * 转换为可比较的时间戳，接受日期、时分秒、时间戳
         * @param {Object} value
         */
        superTimeStamp(value) {
            let dateBase = "";
            if (this.type === "time" && value && typeof value === "string") {
                const now = new Date();
                const year = now.getFullYear();
                const month = now.getMonth() + 1;
                const day = now.getDate();
                dateBase = year + "/" + month + "/" + day + " ";
            }
            if (Number(value)) {
                value = parseInt(value);
                dateBase = 0;
            }
            return this.createTimeStamp(dateBase + value);
        },

        /**
         * 解析默认值 value，字符串、时间戳
         * @param {Object} defaultTime
         */
        parseValue(value) {
            if (!value) {
                return;
            }
            if (this.type === "time" && typeof value === "string") {
                this.parseTimeType(value);
            } else {
                let defaultDate = null;
                defaultDate = new Date(value);
                if (this.type !== "time") {
                    this.year = defaultDate.getFullYear();
                    this.month = defaultDate.getMonth() + 1;
                    this.day = defaultDate.getDate();
                }
                if (this.type !== "date") {
                    this.hour = defaultDate.getHours();
                    this.minute = defaultDate.getMinutes();
                    this.second = defaultDate.getSeconds();
                }
            }
            if (this.hideSecond) {
                this.second = 0;
            }
        },

        /**
         * 解析可选择时间范围 start、end，年月日字符串、时间戳
         * @param {Object} defaultTime
         */
        parseDatetimeRange(point, pointType) {
            // 时间为空，则重置为初始值
            if (!point) {
                if (pointType === "start") {
                    this.startYear = 1920;
                    this.startMonth = 1;
                    this.startDay = 1;
                    this.startHour = 0;
                    this.startMinute = 0;
                    this.startSecond = 0;
                }
                if (pointType === "end") {
                    this.endYear = 2120;
                    this.endMonth = 12;
                    this.endDay = 31;
                    this.endHour = 23;
                    this.endMinute = 59;
                    this.endSecond = 59;
                }
                return;
            }
            if (this.type === "time") {
                const pointArr = point.split(":");
                this[pointType + "Hour"] = Number(pointArr[0]);
                this[pointType + "Minute"] = Number(pointArr[1]);
                this[pointType + "Second"] = Number(pointArr[2]);
            } else {
                if (!point) {
                    pointType === "start" ? (this.startYear = this.year - 60) : (this.endYear = this.year + 60);
                    return;
                }
                if (Number(point)) {
                    point = parseInt(point);
                }
                // datetime 的 end 没有时分秒, 则不限制
                const hasTime = /[0-9]:[0-9]/;
                if (
                    this.type === "datetime" &&
                    pointType === "end" &&
                    typeof point === "string" &&
                    !hasTime.test(point)
                ) {
                    point = point + " 23:59:59";
                }
                const pointDate = new Date(point);
                this[pointType + "Year"] = pointDate.getFullYear();
                this[pointType + "Month"] = pointDate.getMonth() + 1;
                this[pointType + "Day"] = pointDate.getDate();
                if (this.type === "datetime") {
                    this[pointType + "Hour"] = pointDate.getHours();
                    this[pointType + "Minute"] = pointDate.getMinutes();
                    this[pointType + "Second"] = pointDate.getSeconds();
                }
            }
        },

        // 获取 年、月、日、时、分、秒 当前可选范围
        getCurrentRange(value) {
            const range = [];
            for (let i = this["min" + this.capitalize(value)]; i <= this["max" + this.capitalize(value)]; i++) {
                range.push(i);
            }
            return range;
        },

        // 字符串首字母大写
        capitalize(str) {
            return str.charAt(0).toUpperCase() + str.slice(1);
        },

        // 检查当前值是否在范围内，不在则当前值重置为可选范围第一项
        checkValue(name, value, values) {
            if (values.indexOf(value) === -1) {
                this[name] = values[0];
            }
        },

        // 每个月的实际天数
        daysInMonth(year, month) {
            // Use 1 for January, 2 for February, etc.
            return new Date(year, month, 0).getDate();
        },

        /**
         * 生成时间戳
         * @param {Object} time
         */
        createTimeStamp(time) {
            if (!time) return;
            if (typeof time === "number") {
                return time;
            } else {
                time = time.replace(/-/g, "/");
                if (this.type === "date") {
                    time = time + " " + "00:00:00";
                }
                return Date.parse(time);
            }
        },

        /**
         * 生成日期或时间的字符串
         */
        createDomSting() {
            const yymmdd = this.year + "-" + this.lessThanTen(this.month) + "-" + this.lessThanTen(this.day);

            let hhmmss = this.lessThanTen(this.hour) + ":" + this.lessThanTen(this.minute);

            if (!this.hideSecond) {
                hhmmss = hhmmss + ":" + this.lessThanTen(this.second);
            }

            if (this.type === "date") {
                return yymmdd;
            } else if (this.type === "time") {
                return hhmmss;
            } else {
                return yymmdd + " " + hhmmss;
            }
        },

        /**
         * 初始化返回值，并抛出 change 事件
         */
        initTime(emit = true) {
            this.time = this.createDomSting();
            if (!emit) return;
            if (this.returnType === "timestamp" && this.type !== "time") {
                this.$emit("change", this.createTimeStamp(this.time));
                this.$emit("input", this.createTimeStamp(this.time));
                this.$emit("update:modelValue", this.createTimeStamp(this.time));
            } else {
                this.$emit("change", this.time);
                this.$emit("input", this.time);
                this.$emit("update:modelValue", this.time);
            }
        },

        /**
         * 用户选择日期或时间更新 data
         * @param {Object} e
         */
        bindDateChange(e) {
            const val = e.detail.value;
            this.year = this.years[val[0]];
            this.month = this.months[val[1]];
            this.day = this.days[val[2]];
        },
        bindTimeChange(e) {
            const val = e.detail.value;
            this.hour = this.hours[val[0]];
            this.minute = this.minutes[val[1]];
            this.second = this.seconds[val[2]];
        },

        /**
         * 初始化弹出层
         */
        initTimePicker() {
            if (this.disabled) return;
            const value = fixIosDateFormat(this.time);
            this.initPickerValue(value);
            this.visible = !this.visible;
        },

        /**
         * 触发或关闭弹框
         */
        tiggerTimePicker(e) {
            this.visible = !this.visible;
        },

        /**
         * 用户点击“清空”按钮，清空当前值
         */
        clearTime() {
            this.time = "";
            this.$emit("change", this.time);
            this.$emit("input", this.time);
            this.$emit("update:modelValue", this.time);
            this.tiggerTimePicker();
        },

        /**
         * 用户点击“确定”按钮
         */
        setTime() {
            this.initTime();
            this.tiggerTimePicker();
        }
    }
};
</script>

<style lang="scss">
$uni-primary: #007aff !default;

.uni-datetime-picker {
    /* #ifndef APP-NVUE */
    /* width: 100%; */
    /* #endif */
}

.uni-datetime-picker-view {
    height: 130px;
    width: 270px;
    /* #ifndef APP-NVUE */
    cursor: pointer;
    /* #endif */
}

.uni-datetime-picker-item {
    height: 50px;
    line-height: 50px;
    text-align: center;
    font-size: 14px;
}

.uni-datetime-picker-btn {
    margin-top: 60px;
    /* #ifndef APP-NVUE */
    display: flex;
    cursor: pointer;
    /* #endif */
    flex-direction: row;
    justify-content: space-between;
}

.uni-datetime-picker-btn-text {
    font-size: 14px;
    color: $uni-primary;
}

.uni-datetime-picker-btn-group {
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    flex-direction: row;
}

.uni-datetime-picker-cancel {
    margin-right: 30px;
}

.uni-datetime-picker-mask {
    position: fixed;
    bottom: 0px;
    top: 0px;
    left: 0px;
    right: 0px;
    background-color: rgba(0, 0, 0, 0.4);
    transition-duration: 0.3s;
    z-index: 998;
}

.uni-datetime-picker-popup {
    border-radius: 8px;
    padding: 30px;
    width: 270px;
    /* #ifdef APP-NVUE */
    height: 500px;
    /* #endif */
    /* #ifdef APP-NVUE */
    width: 330px;
    /* #endif */
    background-color: #fff;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    transition-duration: 0.3s;
    z-index: 999;
}

.fix-nvue-height {
    /* #ifdef APP-NVUE */
    height: 330px;
    /* #endif */
}

.uni-datetime-picker-time {
    color: grey;
}

.uni-datetime-picker-column {
    height: 50px;
}

.uni-datetime-picker-timebox {
    border: 1px solid #e5e5e5;
    border-radius: 5px;
    padding: 7px 10px;
    /* #ifndef APP-NVUE */
    box-sizing: border-box;
    cursor: pointer;
    /* #endif */
}

.uni-datetime-picker-timebox-pointer {
    /* #ifndef APP-NVUE */
    cursor: pointer;
    /* #endif */
}

.uni-datetime-picker-disabled {
    opacity: 0.4;
    /* #ifdef H5 */
    cursor: not-allowed !important;
    /* #endif */
}

.uni-datetime-picker-text {
    font-size: 14px;
    line-height: 50px;
}

.uni-datetime-picker-sign {
    position: absolute;
    top: 53px;
    /* 减掉 10px 的元素高度，兼容nvue */
    color: #999;
    /* #ifdef APP-NVUE */
    font-size: 16px;
    /* #endif */
}

.sign-left {
    left: 86px;
}

.sign-right {
    right: 86px;
}

.sign-center {
    left: 135px;
}

.uni-datetime-picker__container-box {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 40px;
}

.time-hide-second {
    width: 180px;
}
</style>
